<?php
/**
 * @author		João Batista Neto
 * @package		rpo
 * @subpackage	cert
 * @category	OpenSSL, certificates
 * @license		http://opensource.org/licenses/gpl-license.php GNU Public License
 */
namespace rpo\cert;

use \ArrayObject;
use \UnexpectedValueException;
use \InvalidArgumentException;
use \BadMethodCallException;
use \RuntimeException;
use rpo\base\Object;
use rpo\base\String;
use rpo\cert\interfaces\IOpenSSLCertificateRequest;
use rpo\cert\interfaces\IOpenSSLKey;
use rpo\cert\interfaces\IOpenSSLData;

/**
 * Implementação da requisição de certificado
 * @author		João Batista Neto
 * @package		rpo
 * @subpackage	cert
 * @category	OpenSSL, certificates
 * @license		http://opensource.org/licenses/gpl-license.php GNU Public License
 * @property	string $countryName Duas letras que representam o país
 * @property	string $stateOrProvinceName O estado (ex: São Paulo)
 * @property	string $localityName A cidade (ex: Franca)
 * @property	string $organizationName A organização (ex: Minha empresa)
 * @property	string $organizationalUnitName Seção da empresa (ex: tecnologia)
 * @property	string $commonName Nome comum (ex: meudominio ou João Neto)
 * @property	string $emailAddress Email utilizado para comunicação pela CA
 * @property	string $challengePassword Senha que será utilizada
 */
class OpenSSLCertificateRequest extends Object implements IOpenSSLCertificateRequest {
	/**
	 * @var ArrayObject
	 */
	private $data;

	/**
	 * @var resource
	 */
	private $resource;

	/**
	 * Constroi o objeto de requisição de certificado
	 * @uses ArrayObject
	 */
	public function __construct(){
		$this->data = new ArrayObject();
	}

	/**
	 * Método mágico utilizado para definição das propriedades da requisição de certificado
	 * @param string $name O nome da propriedade
	 * @param string $value O valor da propriedade
	 * @throws UnexpectedValueException Se o valor não for válido
	 * @throws InvalidArgumentException Se não a propriedade não for válida
	 */
	public function __set( $name , $value ){
		if ( is_string( $value ) || ( $value instanceOf String ) ){
			$value = $value instanceOf String ? $value : new String( $value );
			$value->convertEncoding( 'ISO-8859-1' );

			switch ( $name ){
				case 'countryName':
					if ( $value->length == 2 ) $value = $value->toUpperCase();
					else throw new UnexpectedValueException( 'Apenas duas letras são aceitas para Country Name.' );
				case 'stateOrProvinceName':
				case 'localityName':
				case 'organizationName':
				case 'organizationalUnitName':
				case 'commonName':
				case 'emailAddress':
					if ( ( $name == 'emailAddress' ) && !filter_var( $value , FILTER_VALIDATE_EMAIL ) ){
						throw new UnexpectedValueException( 'O email precisa ser válido' );
					}
				case 'challengePassword':
					$this->data[ $name ] = $value;
					break;
				default:
					throw new InvalidArgumentException( sprintf( '%s não é um campo válido.' , $name ) );
			}
		} else {
			throw new UnexpectedValueException( 'O valor do campo precisa ser uma string' );
		}
	}

	/**
	 * Recupera o valor de uma propriedade
	 * @param string $name O nome da propriedade
	 * @return String O valor definido
	 * @throws InvalidArgumentException Se a propriedade não for válida
	 */
	public function __get( $name ){
		$ret = null;

		switch ( $name ){
			case 'countryName':
			case 'stateOrProvinceName':
			case 'localityName':
			case 'organizationName':
			case 'organizationalUnitName':
			case 'commonName':
			case 'emailAddress':
			case 'challengePassword':
				if ( isset( $this->data[ $name ] ) ) {
					$ret = $this->data[ $name ];
				}
				break;
			default:
				throw new InvalidArgumentException( sprintf( '%s não é um campo válido.' , $name ) );
		}

		return $ret;
	}

	/**
	 * Exporta a requisição de certificado
	 * @return string A representação da requisição de certificado como string para ser enviada à CA
	 * @see IOpenSSLExportable::export()
	 * @throws RuntimeException Se não for possível exportar a requisição de certificado
	 * @todo Permitir a exportação para um arquivo
	 */
	public function export(){
		$ret = null;

		if ( !openssl_csr_export( $this->resource , $ret ) ){
			throw new RuntimeException( openssl_error_string() );
		}

		return $ret;
	}

	/**
	 * Gera a requisição de certificado
	 * @param IOpenSSLKey $key A chave que será utilizada para gerar a requisição de certificado
	 * @param IOpenSSLData $config Configurações utilizadas para gerar a requisição
	 * @throws RuntimeException Se não for possível gerar a requisição de certificado
	 */
	public function generate( IOpenSSLKey $key , IOpenSSLData $config = null ){
		$pkey = $key->getResource();
		$this->resource = openssl_csr_new( $this->data->getArrayCopy() , $pkey , $config == null ? null : $config->getData() );

		if ( $this->resource === false ){
			throw new RuntimeException( openssl_error_string() );
		}
	}

	/**
	 * Recupera o recurso utilizado pelo objeto
	 * @return resource
	 * @see IOpenSSLResource::getResource()
	 * @throws BadMethodCallException Se o certificado ainda não tiver sido gerado
	 */
	public function getResource(){
		$ret = null;

		if ( is_resource( $this->resource ) ){
			$ret = $this->resource;
		} else {
			throw new BadMethodCallException( 'O certificado ainda não foi gerado.' );
		}

		return $ret;
	}

	/**
	 * Recupera o nome alternativo para ser utilizado na variável de ambiente SAN
	 * @return string
	 * @throws BadMethodCallException Se o Common Name ou Email Address não estiver definido
	 */
	public function getSAN(){
		$ret = null;

		if ( isset( $this->data[ 'commonName' ] ) && isset( $this->data[ 'emailAddress' ] ) ){
			$uri = sprintf( 'http%s://%s/%s' , isset( $_SERVER[ 'HTTPS' ] ) ? 's' : null , $_SERVER[ 'SERVER_NAME' ] , $this->data[ 'commonName' ] );
			$ret = sprintf( 'SAN=URI:%s,email:%s' , $uri , $this->data[ 'emailAddress' ] );
		} else {
			throw new BadMethodCallException( 'Common Name ou Email Address não está definido.' );
		}

		return $ret;
	}
}